Building plug-in tools for SICK Nova
SICK AppSpace apps built on SICK Nova can be extended with new tools developed as Nova plug-ins. This page describes how to use the plug-in API to develop tools for SICK Nova releases with API version 2.9.0.
For the detailed API reference, see Tool API 2.9.0.
Note
The tool plug-in API may be subject to change and complete forward compatibility with future releases is not guaranteed.
Requirements
A SICK Nova variant.
SICK AppStudio (>= 3.0.0) with valid license, for plug-in tool development.
SICK AppManager (>= 1.4.0), for deployment of plug-in tools and SICK Nova.
Overview
Plug-in tools are implemented as individual SICK AppSpace apps. Communication between the SICK Nova SensorApp, the plug-in tool and its user interface is abstracted by the tool plug-in API, which is made available to the plug-in tool at run-time.
There is a collection of sample tools available on SICK AppPool. The sample tools are intended as examples on how to use the tool API. The Sample Blob Counter-tool will serve as reference in this document.
For viewing the source code of the sample tools:
Deploy one or several Nova Sample Tools on the device (or emulator) in SICK AppManager.
Open SICK AppStudio and connect to the device.
Transfer the sample apps to your working directory in AppStudio (the apps are shown under the Device tab).
Access the script component of the app to view the source code.
When creating your own tool, you can generate a template for it using the Tool generator.
App content
Hint
“MyTool” in this documentation is a placeholder for the app name of your tool.
App content in AppStudio
The tool plug-in API is enabled by the module Nova.Tool,
which should be loaded by the tool script file. This is described in
MyTool file.
Tool plug-in apps have the following components:
resources: includes icon, language file and help text for user interface. See Resources.
scripts: include the source code of the plug-in tool. See Scripts.
project file: Specifies App metadata. See Project file.
Project file
The project file, at "MyTool/project.mf.xml" is automatically
generated when an app is created in AppStudio, but must be adapted for
Nova.
The project-file contains meta-information about the app, such as its
protection levels, version, entry point and its served functions and
events. For your plug-in tool to work, the "project.mf.xml" must
have specific content which is similar for all tools.
Hint
When using the Tool generator, the project.mf.xml will be created with the correct information
This is a template for the project-file for a Nova tool:
<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<manifest>
<application name="MyTool">
<crown name="MyTool">
<desc></desc>
<serves>
<event name="ToolEvent">
<desc></desc>
<param name="arg" type="string" desc=""/>
</event>
<function name="command">
<desc></desc>
<param name="cmd" type="string" desc=""/>
<param name="arg" type="auto" multiplicity="?" desc=""/>
<return name="result" type="auto" multiplicity="?" desc=""/>
</function>
<function name="ui">
<desc></desc>
<param name="cmd" type="string" desc=""/>
<param name="arg" type="auto" desc=""/>
<return name="result" type="string" desc=""/>
</function>
</serves>
</crown>
<meta key="author">TemplateAuthor</meta>
<meta key="version">0.0.0</meta>
<meta key="priority">low</meta>
<meta key="read-protected">false</meta>
<meta key="copy-protected">false</meta>
<meta key="LuaLoadAllEngineAPI">true</meta>
<entry path="scripts" default="Bootstrap.lua"/>
</application>
</manifest>
Follow these steps to get a correct project file for your tool (or use the Tool generator):
Open the “MyTool/project.mf.xml” created by AppStudio in a text editor (notepad, Visual Studio Code, etc.)
Copy the content from the
<meta key="author">...</meta>text and paste it somewhere so you can retrieve it laterReplace the “project.mf.xml” with the template content above
Replace all instances of “MyTool” in the template with the name of your app.
Replace “TemplateAuthor” with the original author from step 2.
Note
Replacing the project file with the template above will set read protection and copy protection to false. Make sure to set protection levels according to your preferences after replacing the project file.
Note
Served events and functions are identical for all tools and do not need to be modified by the tool developer.
Resources
Resources include help text, user interface labels and tool icon, which make your tool easier to understand and to use.
Help Text resources
The help text is a description of the tool and its parameters and results which is accessible directly in the user interface. Multiple versions of the help text for a tool can be provided, one for each language supported by SICK Nova.
If a help text-file for the chosen language is provided, the help text will be accessible in the tool panel in the user interface as in this image:
To add help text add a folder “resources/help/” with help text files with the correct names according to this table:
Language |
Help text file |
|---|---|
English |
en.html |
French |
fr.html |
German |
de.html |
Italian |
it.html |
Chinese |
zh.html |
Spanish |
es.html |
Japanese |
ja.html |
Korean |
ko.html |
Hint
When using the Tool generator, a blank English help
text-file "resources/help/en.html" will be added
automatically. This file can be copied and modified for each
language you wish to support.
Language resource files
The SICK Nova user interface can be viewed in different languages. Plug-in tools can use resource files for translations and to specify readable names for tool features (like parameters). See Localization for details.
Hint
The Tool generator creates the English user interface
labels file "resources/lang/en.json". The translations
should be filled in by the tool developer.
Icon resource file
Add an icon to your tool by adding an image file in png or svg format
to resources/, with the same name as your tool. The icon size
should be 48x48 pixels with transparent background. The
Tool generator generates unique example icons for tools.
Scripts
The scripts/ folder for a plug-in should have the files described
in this section.
Bootstrap file
The file “scripts/Bootstrap.lua” is used for the start-up procedure of SICK Nova and is identical for all tool apps.
It should have the following content:
if _G["NovaMain"] == nil then _G["NovaMain"] = require("API.NovaMain") end _G["Script"].register("NovaMain.InitApps", function() load(_G["NovaMain"].bootstrap(), "bootstrap", "t")() --luacheck:ignore 113 _G["_nova_bootstrap_run_app"](_APPNAME, 2) end)
Hint
The Tool generator creates a bootstrap file with the correct content.
Note
The “
Bootstrap.lua” file should be the the main file of the app:
Setting “Bootstrap.lua” as the main file.
This is specified by <entry path="scripts"
default="Bootstrap.lua"/> in the Project file.
MyTool file
Add your own tool file “scripts/MyTool.lua”. The name of this file should be the same as the name of the app, and with the extension “.lua”. All mentioned script content in this documentation should go into this file.
Hint
Using the Tool generator, a template for “scripts/MyTool.lua” is added automatically.
Tool registration
Tools register themselves to provide the SICK Nova SensorApp with the necessary information about them.
Add the following line at the top of “MyTool.lua” to include the
Nova.Tool module:
local Tool = require("Nova.Tool")
Register the tool by calling
Tool.register with the following
structure:
local _MyTool = Tool.register{
name="My tool",
category="Analysis",
regions=true,
hasOverlays=true,
parameters={},
results={},
thresholds={},
}
Note
The call to Tool.register is made
with curly brackets: {}.
Here are some commonly used registration fields for
Tool.register. See
Tool Registration Arguments for the full list.
Registration field |
Description |
Type |
Required |
|---|---|---|---|
|
A |
|
No, defaults to App name |
|
The tool category. This should be “Analysis” as plug-in support is not established for other tool types. |
|
Yes |
|
Defines if the tool supports regions, and if so which types. Setting this to
|
|
No, defaults to |
|
Allow the tool to produce visualization overlays shown in the viewer. |
|
No, defaults to |
|
The parameters that control the tool. Described in section Parameters. |
Parameter list |
No |
|
The results produced by the tool. Described in section Results and thresholds. |
Result list |
No |
|
Thresholds for results with user-configurable pass/fail ranges. Described in section Results and thresholds. |
Threshold list |
No |
The returned item from Tool.register is the custom tool
class for the tool.
Parameters
Parameters control the tool execution. Depending on the type of parameter, a suitable control is included in the user interface settings pane of the tool to allow for configuration by the user.
Parameters are instance-specific. This means that if you add two instances of a tool in the user interface, changing a parameter for one of them will not change the corresponding parameter of the other instance.
Parameter values are saved in exported configurations so that tools are reconstructed with the same values when a configuration is imported.
A parameter is added as a dictionary-like Lua-table. The possible entries are given below:
Entry |
Required |
Entry type |
|---|---|---|
|
Yes |
string |
|
Yes |
|
|
Depends on |
Depends on |
|
Depends on |
Depends on |
|
Depends on |
Depends on |
|
Depends on |
Depends on |
|
No |
Table, see Optional parameters |
See also:
Parameter types for the supported values for the “type” entry
Parameter groups for grouping parameters within a collapsible section in the user interface
Optional parameters for parameters that have an enable/disable toggle controlling if they have an effect
Parameter section example
Below is an example of a parameter section and the corresponding appearance in the user interface:
parameters={
{name="IntensityThreshold", type="intensity8range", default={0, 200}},
{name="PostProcess", type="bool", default=false},
{name="Filter", type="enum", values={"No", "Gaussian", "Median"}, default="No"},
{name="Iterations", type="int", range={2, 10}, ui={style="slider"}}
See the sample tools and Parameter types for more examples of how parameters are specified.
Results and thresholds
For each execution step, tools produce results. Like parameters, the result values are instance specific and different results can have different types. The results of the tool can also be accessed by other tools and communicated to external devices. Certain results can also be visualized with overlays in the image view using the presentation API described in Presentation
Results are declared in the results-section of Tool.register. A result entry has the following fields:
Field |
Required |
Description |
|---|---|---|
name |
Yes |
The label for the result |
type |
Yes |
The data type for the result |
ui |
No |
Options for user-interface representation |
Values for all results specified in the result-section of
Tool.register should be set in the execute-function using
output:add, see
SampleBlobCounter execute.
Each tool must have a Pass result. This is of type bool and
can be interpreted as the overall result of the tool — a combination
of the other results. To allow for this, numerical results can have a
corresponding threshold that returns true if the result is within
the specified interval.
Note
The threshold for a result must have the same name as the result.
Below is an example of a result and a corresponding threshold section.
The result of Pass for this example is written to depend on
whether the result Score is within its corresponding thresholds.
results={
{name="Score", type="float"},
{name="Pass", type="bool"}},
thresholds={
{name="Score", type="percentrange", default={80.0, 100.0}}
The images below show the user interface appearance in the two cases
of "Pass" = true and "Pass" = false.
It is not necessary to have a corresponding threshold for a result. Without a threshold the result will be shown in the user interface as a text label with the value, regardless of its type. An example of this is shown with the code snippet and result section below.
results={
{name="Score", type='int'},
{name="Pass", type='bool'}},
thresholds={}
A result can be omitted from the user interface (but still available
for dependent tools) by declaring it with ui=false:
{name="SomeResult", type="float", ui=false}
See also
Localization for readable names or translations for parameters, thresholds and results in the user interface
Result types for the available result types
Threshold types for the available threshold types
MyTool.execute
The execute() method implements the
core functionality of a tool. It is called for each instance of the
tool every time a new image acquired or a parameter is changed.
The execute method should perform the tool evaluation using the configured parameters. It should communicate results and, if meaningful, add overlay graphics.
A tool must have an execute()-method. A template implementation is
added automatically if the Tool generator is used.
The execute method should have this signature:
function _MyTool:execute(input, output)
inputcontains the image and other parameters needed for the execution of the plug-in tooloutputis where the results of the plug-in tool are added.
Besides using the Nova interfaces input and
output, tools are implemented using the SICK AppSpace CROWN
API. This means that most CROWNs available in the used firmware of the
device can be used within the execute method.
For details on the SICK AppSpace CROWN API, see the documentation included in firmware releases on https://support.sick.com/
The subset of Crowns that are used in the Nova interface itself are documented here: AppSpace Crown API.
Accessing provider data components
Different devices can produce different kinds of data, for instance height data, intensity data, or color (RGB) data.
To access different data components, first add the following line at the top
of “MyTool.lua” to include the Nova.Types module:
local Types = require("Nova.Types")
The example below shows how to check if a component is present and then access it
from the execute method:
if not input:hasProviderComponent(Types.DataComponent.Intensity) then
output.log.warning("ErrorIncorrectTypeImage")
return setResults()
end
local intensityImage = input:getProviderData(Types.DataComponent.Intensity)
It is recommended to check for existing data components and directly fail the tool if no usable component is available.
See Nova.Types.DataComponent for a full list of components available
for different Nova products. Note that for some products it is possible to
enable/disable components in the Acquisition settings.
Currently all calls to input:getProviderData
will return an Image.
For backwards compatibility, input:getImage can be
called to get the intensity data component.
SampleBlobCounter execute
Below is a walkthrough of _SampleBlobCounter.execute(). Please see
the code for full details and context.
Extract the
presentationinterface:local presentation = output:getPresentation()
Extract the image from
inputlocal image = input:getImage()
Declare initial values for the results
local numBlobs = 0 local pass = false
Create a local function
setResults()for setting the results tooutputlocal function setResults() output:add("NumBlobs", numBlobs) output:add("Pass", pass) end
Get the region (possibly transformed by the parent tool) from
input. Abort execution if the net region is emptylocal region, onlyNegative = input:getTransformedRegion() presentation:addRegion(region) if onlyNegative then return setResults() end
Perform tool specific image processing
local roiPixelRegion = region:toPixelRegion(image) -- ...
Add objects to be overlaid in the viewer to the
presentationinterfacepresentation:add(blobs) presentation:add(cogs)
Set results locally, using
thresholdsto determine if the result is successfulnumBlobs = #blobs pass = self.thresholds.NumBlobs:contains(numBlobs)
Add local results to
outputsetResults()
Presentation
Tools can add overlays to the viewer using the presentation
interface. The presentation interface is retrieved from
output as in step 1 in
SampleBlobCounter execute.
The overlays can be text, regions or shapes. The tool region (if any) should be added to allow editing it, as in step 5 in SampleBlobCounter execute.
For the list of functions and a usage example, see the reference
documentation for the presentation interface.
Use of self for tool instances
Lua provides object-orientation support much like for example C++ or
Java. Tool instances can therefore be viewed as different instances of
the same tool class with associated variables and functions (members).
The keyword in Lua for accessing members associated with an instance
is self. self is a Lua-table and members can be accessed
accordingly.
To begin with, all entries provided in the tool registration gets associated with each instance.
For example:
local _MyTool = Tool.register{
name ="MyTool",
-- ...
parameters={
{name="MyBool", type='bool', default=true}
},
-- ...
}
local _MyTool:execute(input, output)
print(self.name) -- "MyTool"
end
Parameters can be accessed similarly:
local localBool = self.parameters.MyBool
-- localBool now holds the current value of the parameter "MyBool" for the particular instance.
Constructor and destructor
A constructor and a
destructor method can be defined for
a tool. The constructor is called automatically each time a new
instance of the tool is created. The destructor is called
automatically each time an instance is destroyed. Instances are
created and destroyed both when the tool is added or removed by a user
as well as during job switch or configuration import.
The constructor is useful if there are instance parameters that should not be shown in the user interface, that should not be saved to the configuration and that should persist across executions of the tool. The destructor is useful if there are instance parameters that require additional actions on destruction (e.g. explicit disposal/tear-down).
The constructor and destructor have the following signatures:
function _MyTool:constructor()
function _MyTool:destructor()
All members associated with the instance in Tool.register{} are available in the constructor and the
destructor.
Differences between 2D and 3D
The example shown in SampleBlobCounter execute is for a 2D tool. When making a 3D tool there are a few things that should be considered:
When calling
Tool.register, use aregions definitionwithis3D=trueUse the API described in Accessing provider data components to access the wanted data components
When getting the region by calling
input:getTransformedRegion, the third return value is the region height rangeTo show the tool region in the viewer, call
presentation:addRegionwith the height range table as second argument
Building and deploying a plug-in tool
Plug-in tools should be packaged as sapk-files for deployment with
SICK AppManager.
In SICK AppStudio, package your plug-ins in an .sapk
In SICK AppManager, remove all old apps from the device
Deploy SICK Nova (.sapk) to the device
Deploy your plug-in tools from step 1 to the device
Restart all apps
Your plug-in tools should now be visible and usable.